﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Messaging;
using System.Threading;
using System.Windows.Forms;

namespace BattleshipGameClient
{
    public class Connection : MarshalByRefObject, IConnection
    {
        public IConnection remoteConnection = null;
        private Dictionary<String,IConnection> viewers = new Dictionary<String,IConnection>();
        private ConnectionForm connectionForm;
        private RequestForm requestForm;
        private Uri myUri;
        private String opponentName = null;
        private Uri remoteUri;
        private bool opponentReadyToStart = false;
        internal Fleet myFleet;
        private Fleet opponentsFleet;
        public enum States { idle, placingFleet, fleetReady, aiming, isTarget, finished };
        private States playingState = States.idle;
        private bool isFirstShooter;
        private GameForm gameForm;

        [STAThread]
        static void Main()
        {
            new Connection();
        }

        private Dictionary<Uri, IConnection> remoteConnections = new Dictionary<Uri, IConnection>();
        private Dictionary<Uri, ChatForm> chats = new Dictionary<Uri, ChatForm>();
        private ChatForm chat_form;

        public String getRemoteName()
        {
            if (opponentName == null)
                opponentName = remoteConnection.getName();
            return remoteConnection.getName();
        }
        public Uri getRemoteUri()
        {
            return remoteUri;
        }

        public string getName()
        {
            return connectionForm.getName();
        }
        public Uri getUri()
        {
            return myUri;
        }

        public Connection()
        {
            startServer();

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            playingState = States.idle;

            // Create the connection Form in its own thread
            connectionForm = new ConnectionForm(this);
            Application.Run(connectionForm);
           
        }

        /// <summary>
        /// This method is used by a remote client to send a message.
        /// </summary>
        /// <param name="name">The senders name</param>
        /// <param name="uri_s">The senders uri</param>
        /// <param name="msg">The message</param>
        /// 
        [OneWay()]
        public void message(String name, String uri_s, String msg, String timestamp)
        {
            Uri uri = new Uri(uri_s);

            ChatForm cf = getChat(uri);

            if (msg != null && timestamp != null)
            {
                connectionForm.showForm(cf);
                DateTime ts = new DateTime();
                ts = DateTime.Parse(timestamp);
                ts.ToLocalTime();
                cf.displayMessage(ts, name, msg);
            }
        }

        internal void openChat()
        {
            if (remoteUri != null)
                connectionForm.showForm(getChat(remoteUri));
        }

        private ChatForm getChat(Uri uri)
        {
            if (!chats.ContainsKey(uri))
            {
                chat_form = new ChatForm(getName(), this, uri);
                chats.Add(uri, chat_form);
            }
            return chats[uri];
        }

        /// <summary>
        /// This method sends a message to a remote client.
        /// </summary>
        /// <param name="uri">The Uri to the remote client</param>
        /// <param name="msg">The message</param>
        public void selfMessage(Uri uri, String msg, DateTime timestamp) {
             getConnection(uri).message(getName(), myUri.AbsoluteUri, msg, timestamp.ToString("u"));
        }


        public void closeChat(Uri uri)
        {
            chats.Remove(uri);
        }
        public void startServer()
        {
            try
            {
                RemotingConfiguration.Configure("BattleshipGameClient.exe.config", false);
                RemotingServices.Marshal(this, "battleship.soap");
                myUri = determineIPAddress();
            }
            catch (Exception)
            {
                MessageBox.Show("Couldn't start up the remoting connection. Please try again later.");
                Application.Exit();
            }
        }


        private Uri determineIPAddress()
        {
           
             // Parsing
             IChannel[] channels = ChannelServices.RegisteredChannels;
             foreach (IChannel c in channels)
             {
                HttpChannel http = c as HttpChannel;
                //System.Runtime.Remoting.Channels.Tcp.TcpChannel tcp = c as System.Runtime.Remoting.Channels.Tcp.TcpChannel;
                if (http != null)
                {
                   ChannelDataStore store = http.ChannelData as ChannelDataStore;
                   if (store != null)
                   {
                      foreach (string s in store.ChannelUris)
                      {
                         return new Uri(s); // There should only be one, and regardless the port should be the same even if there are others in this list.
                      }
                   }
                }
             }

             return null;
        }

        internal int queryPlayerStatus()
        {
            return remoteConnection.getStatus();
        }

        public IConnection getConnection(Uri uri)
        {
            if (!remoteConnections.ContainsKey(uri))
            {
                try
                {
                    Object remoteObject = Activator.GetObject(typeof(IConnection), uri.AbsoluteUri + "battleship.soap");
                    remoteConnection = (IConnection)remoteObject;
                    getRemoteName();
                }
                catch (Exception)
                {
                    return null;
                }
                remoteUri = uri;
                remoteConnections.Add(uri, remoteConnection);
                return remoteConnection;
            }
            else
            {
                return remoteConnections[uri];
            }

         }


        public int getStatus()
        {
            if (playingState == States.idle)
            {
                return 1;
            }
            else
            {
                return 2;
            }
        }

        [OneWay()]
        public void requestNewGame(String uri, String name, bool firstShooter)
        {
            requestForm = new RequestForm(new Uri(uri), name, firstShooter, this);
            connectionForm.showForm(requestForm);
        }

        public void selfRequestNewGame()
        {
            isFirstShooter = decideFirstShooter();
            getConnection(getRemoteUri()).requestNewGame(myUri.AbsoluteUri, getName(),!isFirstShooter);
        }

        /// <summary>
        /// Called remotely when the other client accepts the request to start a new game 
        /// </summary>
        /// <param name="uri">Then calling client's Uri</param>
        /// 
        [OneWay()]
        public void acceptNewGame(String uri_s)
        {
            Uri uri = new Uri(uri_s);
            remoteConnection = getConnection(uri);
            Thread fre = new Thread(new ThreadStart(start_game_form));
            fre.IsBackground = true;
            fre.Start();
            playingState = States.placingFleet;
        }

        private void start_game_form()
        {
            myFleet = new Fleet(false);
            opponentsFleet = new Fleet(true);

            gameForm = new GameForm(this);
            Application.Run(gameForm);
        }

        /// <summary>
        /// Called internaly when we accept the other players request to start a new game.
        /// </summary>
        /// <param name="remoteUri">The requesting player's Uri</param>
        public void selfAcceptNewGame(Uri remoteUri, bool isFirstShooter)
        {
            if (playingState != States.idle)
            {
                MessageBox.Show("Cannot accept a new game when already playing one");
                return;
            }
            
            this.isFirstShooter = isFirstShooter;

            getConnection(remoteUri).acceptNewGame(myUri.AbsoluteUri);

            Thread fre = new Thread(new ThreadStart(start_game_form));
            fre.IsBackground = true;
            fre.Start();
            playingState = States.placingFleet;
        }

        [OneWay()]
        public void denyNewGame(String uri_s, String name)
        {
            Uri uri = new Uri(uri_s);
            if (name == "")
                name = "Anonymous";
            connectionForm.showForm(new Message("New Game Denied", name + " denied your request to start a game."));
            
        }
        public void selfDenyNewGame(Uri uri)
        {
            getConnection(uri).denyNewGame(myUri.AbsoluteUri,getName());
        }

        public string viewGame(String uri)
        {
            throw new NotImplementedException();
        }

        [OneWay()]
        public void readyToStart()
        {
            if (playingState == States.placingFleet)
                gameForm.setOpponentReadyToStart();
            else if (playingState == States.fleetReady)
            {
                if (isFirstShooter)
                    playingState = States.aiming;
                else
                    playingState = States.isTarget;

                gameForm.startShooting();
            }

            opponentReadyToStart = true;
        }

        [OneWay()]
        public void fleetReport(Fleet fleet)
        {
            throw new NotImplementedException();
        }

        public hitReport shoot(Point coord)
        {
            Ship shot = myFleet.shoot(coord);
            
            playingState = States.aiming;

            if (myFleet.isSunk())
                displayWinner(false);

            hitReport hr;

            if (shot == null)
            {
                hr = new hitReport(coord, Ship.Type.None, false);
            }
            else
            {
                hr = new hitReport(coord, shot.getShipType(), shot.IsSunk);
            }

            gameForm.hitDisplay(hr, true);

            

            return hr;

            //foreach (IConnection viewer in viewers)
            //    viewer.hitReport(coord, "miss");


        }

        public void registerViewer(IConnection viewer)
        {
            throw new NotImplementedException();
        }


        public bool decideFirstShooter()
        {
            Random random = new Random();
            if (random.Next(0, 2) == 0)
                return true;
            else
                return false;
        }


        private void displayWinner(bool winnerIsMe)
        {
            playingState = States.finished;

            Message.MessageCallback mc = new Message.MessageCallback(endGame);
            if (winnerIsMe)
                connectionForm.showForm(new Message("Game Finished","Congratulations! You won the game.",mc));
            else
                connectionForm.showForm(new Message("Game Finished","Your opponent " + opponentName + " won the game.",mc));
        }

        internal void selfShoot(Point point)
        {
            hitReport shot = remoteConnection.shoot(point);

            gameForm.hitDisplay(shot);

            if (shot.getShipType() != Ship.Type.None)
            {
                opponentsFleet.hit(shot.getCoord(), shot.getShipType());
            }

            playingState = States.isTarget;

            if (opponentsFleet.isSunk())
            {
                displayWinner(true);
            }
        }



        internal void selfReadyToStart()
        {
            if (playingState == States.placingFleet)
                remoteConnection.readyToStart();

            playingState = States.fleetReady;

            if (opponentReadyToStart)
            {
                if (isFirstShooter)
                    playingState = States.aiming;
                else
                    playingState = States.isTarget;

                gameForm.startShooting();
            }
        }

        internal States getState()
        {
            return playingState;
        }

        internal void endGame()
        {
            gameForm.BeginInvoke(new MethodInvoker(gameForm.Dispose));
            gameForm = null;
            myFleet = null;
            opponentsFleet = null;
            remoteConnection = null;
            playingState = States.idle;
        }

        public void hitReport(hitReport hit)
        {
            // Not implemented
            return;
        }
    }
}
